package com.zdp.es.service.impl;

import com.alibaba.fastjson.JSON;
import com.zdp.es.entity.es.HotelDoc;
import com.zdp.es.ro.HotelRequestRO;
import com.zdp.es.service.IHotelService;
import com.zdp.es.vo.PageResult;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;

/**
 * @author ZhengDp
 * @Date 2023/9/10 12:53
 */
@Service
public class HotelServiceImpl implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult search(HotelRequestRO params) {
        SearchRequest request = new SearchRequest("hotel");
        buildBasicQuery(params, request);
        // 分页
        int page = params.getPage();
        int size = params.getSize();
        request.source().from((page - 1) * size).size(size);

        // 按地理位置排序
        String location = params.getLocation();
        if(StringUtils.isNotBlank(location)) {
            request.source().sort(
                    SortBuilders.geoDistanceSort("location",new GeoPoint(params.getLocation()))
                            .order(SortOrder.ASC)
                            .unit(DistanceUnit.KILOMETERS)
            );
        }



        // 发送请求
        try {
            SearchResponse search = client.search(request, RequestOptions.DEFAULT);
            // 解析响应
            List<HotelDoc> res = parseResponse(search, HotelDoc.class, HotelDoc::setDistance);
            return new PageResult((long) res.size(), res);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void buildBasicQuery(HotelRequestRO params, SearchRequest request) {
        // 准备DSL
        String key = params.getKey();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 关键字搜索
        if (StringUtils.isNotBlank(key)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", key));
        }
        // 城市条件
        if (StringUtils.isNotBlank(params.getCity())) {
            // filter 不参与算分
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        if (StringUtils.isNotBlank(params.getBrand())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        // 价格
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }

        // 广告置顶 - 算法函數
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                /*原始查询，相关性算法*/
                boolQueryBuilder,
                /*算法函数数组*/
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                /*过滤条件*/
                                QueryBuilders.termQuery("isAD",true),
                                /* 算分函数，只有被过滤出来的文档才会执行算法函数 */
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }
        );

        request.source().query(functionScoreQuery);
    }


    private <T> List<T> parseResponse(SearchResponse response, Class<T> clazz,MyFunction<T,Object> function) {
        SearchHits searchHits = response.getHits();

        long total = searchHits.getTotalHits().value;
        List<T> res = Lists.newArrayList();
        System.out.println("共搜索到" + total + "条文档");
        // 获取文档数据
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();

            T temp = JSON.parseObject(json, clazz);
            // 获取排序值
            Object[] sortValues = hit.getSortValues();
            if(!CollectionUtils.isEmpty(sortValues)) {
                function.function(temp,sortValues[0]);
            }
            res.add(temp);
        }
        return res;
    }

    interface MyFunction<T,R> {
        void function(T source,R target) ;
    }

}
